package com.foodev.maven.gwt;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;

/**
 * @author Martin Algesten
 */
public abstract class AbstractGwtMojo extends AbstractMojo {

  
  public static final String OS_NAME =
    System.getProperty( "os.name" ).toLowerCase( Locale.US );
  

  protected static boolean isWindows() {
    return OS_NAME.startsWith("windows");
  }
  protected static boolean isLinux() {
    return OS_NAME.startsWith("linux");
  }
  protected static boolean isMac() {
    return OS_NAME.startsWith("mac");
  }
  
  
  /**
   * @parameter expression="${project.build.directory}/gwt"
   */
  protected File gwtHome;

  
  /**
   * Source files for the war, this is where we the plugin compiles to.
   * In a version managed directory it's advised to add ignores for the
   * resulting modules and make be created as part of the build.
   *
   * @parameter expression="${basedir}/src/main/webapp"
   * @required
   */
  protected File warSourceDirectory;
  
  
  /**
   * The maven project.
   * @parameter expression="${project}"
   * @required
   */
  protected MavenProject project;

  
  /**
   * Constructs a classpath from <code>compileSourceRoots + 
   * compileClasspathElements + context classloader classpath</code>.
   * @return the total classpath as a list of strings. No filtering is done,
   * the called must do that with filterClasspath().
   * @throws MojoExecutionException if context classloader or local 
   * classloader is not of type <code>URLClassLoader</code>
   * @throws MalformedURLException
   */
  @SuppressWarnings("unchecked")  
  protected List<String> buildClasspath() throws MojoExecutionException,
    DependencyResolutionRequiredException {
    
    HashSet<String> cp = new HashSet<String>();
        
    cp.addAll( project.getCompileSourceRoots() );
    
    Map<String,MavenProject> projectRefs = project.getProjectReferences();
    
    for ( MavenProject refProj : projectRefs.values() ) {
      System.out.println( refProj.getBuild().getOutputDirectory() );
      cp.add( refProj.getBuild().getOutputDirectory() );
    }

    cp.addAll( project.getCompileClasspathElements() );
    cp.addAll( project.getRuntimeClasspathElements() );
    cp.addAll( project.getTestClasspathElements() );
    
    if ( Thread.currentThread().getContextClassLoader() instanceof URLClassLoader ) {
      cp.addAll( urlToString(Arrays.asList(((URLClassLoader)
          Thread.currentThread().getContextClassLoader()).getURLs())) );
    } else if ( getClass().getClassLoader() instanceof URLClassLoader ) {
      cp.addAll( urlToString(Arrays.asList(((URLClassLoader)
          getClass().getClassLoader()).getURLs())) );
    } else {
      throw new MojoExecutionException("Failed to get URLClassLoader for plugin" );
    }
         
    return new LinkedList<String>(cp);
    
  }
  

  /**
   * Executes the given command in the given working directory.
   * @param workingDir the working directory
   * @param cmd the command with arguments.
   * @return the exit value from the process.
   * @throws IOException
   */
  protected int executeProcess( File workingDir, String[] cmd )
    throws IOException {
    
    ProcessBuilder pb = new ProcessBuilder( cmd );
    pb.directory(workingDir);
    
    pb.redirectErrorStream(true);
    
    Process proc = pb.start();

    InputStream is = proc.getInputStream();
    InputStreamReader isr = new InputStreamReader(is);
    BufferedReader br = new BufferedReader(isr);
    
    String line;
    
    while( (line = br.readLine()) != null ) {
      if ( getLog().isDebugEnabled() ) {
        getLog().debug( line );
      } else {
        getLog().info( line );
      }
    }
    
    try {
      proc.waitFor();      
    } catch ( Exception e ) {
    }
    
    return proc.exitValue();
      
  }

  
  private final static String ZIPREGEX = 
    ".*[\\\\/]gwt-dev-[^-]+-(mac|linux|windows)-libs\\.zip$";

  // Note that 'target/classes' needs to be on the classpath for
  // the shell, but not for the compilation.
  private final static String[] FILTEREDREGEX = new String[] {
    ".*[\\\\/]maven-gwt-plugin-.+\\.jar$",
    ".*[\\\\/]plexus-utils-.+\\.jar$"
  };
  
  /**
   * Tests if a given classpath element matches the regular expression
   * we use to detect the native library zip.
   * @param element the element to test
   * @return true if the given element is the native library zip.
   */
  protected boolean isNativeLibZip( String element ) {
    return element.matches(ZIPREGEX);
  }
  
  
  /**
   * Filter the classpath given. Parameter tells if we are to filter out
   * the 'target/classes' element.
   */
  protected List<String> filterClasspath( List<String> classpath,
      boolean filterBuildOutput ) {

    LinkedList<String> result = new LinkedList<String>(classpath);
    
    for ( Iterator<String> iter = result.iterator(); iter.hasNext(); ) {
      
      String element = iter.next();
      
      if ( isNativeLibZip(element) || isFiltered(element, filterBuildOutput) ) {
        iter.remove();
      }
          
    }
    
    return result;

  }
  
  
  /**
   * Tests if the given classpath element matches a regular expression
   * for filtered elements such as plexus-utils and maven-gwt-plugin.
   * @param element the element to test
   * @return true if the given element is filtered.
   */
  private boolean isFiltered( String element, boolean filterBuildOutput ) {
    String localBuildOutput = project.getBuild().getOutputDirectory();
    String testBuildOutput = project.getBuild().getTestOutputDirectory();
    for ( int i = 0; i < FILTEREDREGEX.length; i++ ) {
      if ( element.matches(FILTEREDREGEX[i]) ||
           filterBuildOutput && ( element.equals(localBuildOutput) ||
        	   element.equals(testBuildOutput) ) ) return true;
    }
    return false;
  }
  
  
  /**
   * Translates a list of URL to a list of String.
   * @param urls the urls to transate
   * @return a list of strings.
   */
  private List<String> urlToString( List<URL> urls ) {
    
    LinkedList<String> str = new LinkedList<String>();
    
    for( URL url : urls ) {
     
      if ( !url.getProtocol().equals("file") ) {
        getLog().error("Classpath contains non-file URL: "+url.toString() );
        continue;
      }
      
      String path;
      
      if ( isWindows() ) {
	  
	  
        String tmp = url.getPath();
        tmp = tmp.replaceAll(" ", "%20");
        
        try {
            
          url = new URL("file", "", tmp);
          path = new File(url.toURI()).getAbsolutePath();
          
        
        } catch (Exception e) {
          getLog().error(e);
          continue;
        }
          
      } else {
       
        path = url.getPath();
        
      }

      str.add( path );
      
    }
    
    return str;
    
  }
  
  
}
